using System;
using SharpMud.Net;
using SharpMud.Server.Systems.Net;
using SharpMud.Server.Systems.Metadata;
using System.Collections.Generic;
using SharpMud.Net.Telnet.Session;
using System.ComponentModel;

namespace SharpMud.Server.Systems.Net.Telnet
{
	[Name("TelnetServer")]
	[Version("0.8")]
	[Guid("{021E86BB-C0AC-4508-ADD5-6D9B01F101C8}")]
    [Browsable(true), DisplayName("Telnet Server"), Description("The Telnet Server of the application.")]
	public class TelnetServer<TTelnetSession> : Server.Systems.PulsarServerSystem, SharpMud.Server.Systems.Net.INetworkSystem where TTelnetSession : TelnetSessionBase, IHasSessionState<TTelnetSession>, new()
	{
        private readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodInfo.GetCurrentMethod().DeclaringType);

        #region Private Variables
		private List<INetworkConnection>	                    _Connections;
		private List<TTelnetSession>	                        _Sessions;
		private List<ConnectionListener>	                    _Listeners;
		private TelnetServerConfiguration<TTelnetSession>		_Configuration;
		#endregion

		/// <summary>
		/// This event is raised whenever one of this TelnetServer's listeners has encountered a new connection,
		/// after the new connection's session has been established.
		/// </summary>
        public event SharpMud.Net.ConnectionEventHandler ConnectionMade;
		/// <summary>
		/// This method does some work every time one of this TelnetServer's listeners encounters a new
		/// connection.
		/// </summary>
		/// <param name="sender">The listener that has a new connection waiting</param>
		/// <param name="connection">The connection that is waiting</param>
		private void Listeners_ConnectionMade(SharpMud.Net.IConnectionListener sender, SharpMud.Net.INetworkConnection connection)
		{
            if (this.ConnectionMade != null)
                this.ConnectionMade(connection, new SharpMud.Net.ConnectionEventArgs(connection.LocalEndpoint, connection.RemoteEndpoint));

			SharpMud.Net.IHasSessionState<TTelnetSession> ts = ((IHasSessionState<TTelnetSession>)(new TTelnetSession()));

            lock(this._Connections)
            {
			    this._Connections.Add(connection);
                //connection.Disconnected += new ConnectionEventHandler(connection_Disconnected);
            }
            lock(sender)
            {
			    sender.RemoveWaitingConnection(connection);
            }
			ts.Initialize(connection,this);
            lock(this._Sessions)
            {
			    this._Sessions.Add((TTelnetSession)ts);
            }
            ListenerMetadatum<TTelnetSession> lm;
            ISessionState<TTelnetSession> sessionState = GetInitialSessionState(connection.LocalEndpoint);
            if (sessionState==null)
            {
                throw new InvalidOperationException("No default SessionState could be determined for the Telnet session");
            }
            sessionState.Initialize(ts, null);
            ts.SessionState.Push(sessionState);
		}

        private ISessionState<TTelnetSession> GetInitialSessionState(System.Net.IPEndPoint endpoint)
        {
            lock (this._Configuration.ListenersCollection)
            {
                ListenerMetadatum<TTelnetSession> resultListener=null;
                foreach (ListenerMetadatum<TTelnetSession> lm in this._Configuration.ListenersCollection)
                {
                    System.Net.IPEndPoint lmEndPoint = lm.GetEndpoint(this._Configuration.BasePort);
                    if (lmEndPoint.Port == endpoint.Port)
                    {
                        resultListener = lm;
                        break;
                    }
                }
                if (resultListener == null)
                    return null;
                else
                    return ((ISessionState<TTelnetSession>)resultListener.DefaultSessionStateType.AcquireTypeInstance());
            }
        }
		/// <summary>
		/// This method initializes the TelnetServer
		/// </summary>
		/// <exception cref="InvalidOperationException">Thrown if the TelnetServer is attempted to be Initialized() before a configuration is loaded</exception>
		protected override void _Initialize()
		{
			if(this._Configuration == null)
			{
				throw new InvalidOperationException("You cannot initialize a TelnetServer before its configuration is loaded.");
			}
            log.Info("Initializing...");
			base._Initialize();
			_Connections = new List<INetworkConnection>();
            _Sessions = new List<TTelnetSession>();
			_Listeners = new List<ConnectionListener>();
			foreach(ListenerMetadatum<TTelnetSession> lm in this._Configuration.Listeners)
			{
				SharpMud.Net.ConnectionListener cl;
                cl = new ConnectionListener(lm.GetEndpoint(_Configuration.BasePort).Port,this);
				this._Listeners.Add(cl);
				cl.ConnectionMade += new SharpMud.Net.ConnectionListenerEventHandler(Listeners_ConnectionMade);
			}
			log.Info("INITIALIZED");
		}

		/// <summary>
		/// This method does pulse-work for the TelnetServer
		/// </summary>
		protected override void Pulse()
		{
			base.Pulse ();
			lock(this._Listeners)
			{
				foreach(SharpMud.Net.ConnectionListener cl in this._Listeners)
				{
					cl.Pulse();
				}
			}
			lock(this._Connections)
			{
				foreach(SharpMud.Net.INetworkConnection c in this._Connections)
				{
					c.Pulse();
				}
			}
			lock(this._Sessions)
			{
				foreach(TelnetSessionBase s in this._Sessions)
				{
					if(s is System.Threading.IRequiresExternalPulsing)
					{
						(s as System.Threading.IRequiresExternalPulsing).Pulse();
					}
				}
			}
		}

		protected override void _Start()
		{
			log.Debug("Starting...");
			base._Start ();
			lock(this._Listeners)
			{
				foreach(SharpMud.Net.ConnectionListener cl in this._Listeners)
				{
					cl.Start();
				}
			}
			log.Info("STARTED");
		}

		protected override void _Stop()
		{
			log.Debug("Stopping...");
			base._Stop ();
			lock(this._Listeners)
			{
				foreach(SharpMud.Net.ConnectionListener cl in this._Listeners)
				{
					cl.Stop();
				}
			}
            log.Info("STOPPED");
		}

		#region INetworkSystem Members
		public SharpMud.Net.INetworkConnection[] GetConnections()
        {
            return _Connections.ToArray();
		}
        [Browsable(true), DisplayName("Connections"), Description("The Telnet Server's current connections.")]
        public IList<SharpMud.Net.INetworkConnection> Connections
        {
            get
            {
                return ((IList<SharpMud.Net.INetworkConnection>)new List<SharpMud.Net.INetworkConnection>(GetConnections()));
            }
        }
		#endregion

		#region Public Properties
		public SharpMud.Net.Telnet.Session.TelnetSessionBase[] GetSessions()
		{
            return _Sessions.ToArray();
		}
        [Browsable(true), DisplayName("Sessions"), Description("The Telnet Server's current session objects.")]
        public IList<SharpMud.Net.Telnet.Session.TelnetSessionBase> Sessions
        {
            get
            {
                return ((IList<SharpMud.Net.Telnet.Session.TelnetSessionBase>)new List<SharpMud.Net.Telnet.Session.TelnetSessionBase>(GetSessions()));
            }
        }
		#endregion

		#region Configuration
        [Browsable(true), DisplayName("Configuration"), Description("The Telnet Server's configuration.")]
		public TelnetServerConfiguration<TTelnetSession> Configuration
		{
			get
			{
				return this._Configuration;
			}
            set
            {
                this._Configuration = value;
            }
		}

		private const string DefaultConfigurationFileName = "TelnetServer.config.xml";

		[Browsable(true), DisplayName("Default Configuration File"), Description("The default file where the Telnet Server looks for configuration information."),ReadOnly(true)]
        public string DefaultConfigurationFile
		{
			get
			{
				return System.IO.Path.Combine(this.Host.RootConfigurationFolder,DefaultConfigurationFileName);
			}
		}
		public void LoadConfiguration()
		{
			this.RequireServerSystemState(ServerSystemStatus.Uninitialized);
			this.LoadConfiguration(DefaultConfigurationFileName);
		}
		public void LoadConfiguration(string fileName)
		{
			this.RequireServerSystemState(ServerSystemStatus.Uninitialized);
			System.IO.FileStream fs = new System.IO.FileStream(fileName,System.IO.FileMode.Open);
		}
		public void LoadConfiguration(TelnetServerConfiguration<TTelnetSession> configuration)
		{
			this.RequireServerSystemState(ServerSystemStatus.Uninitialized);
			if(configuration==null)
				throw new ArgumentNullException("configuration");

			this._Configuration = configuration;
		}
		public void LoadConfiguration(System.IO.Stream stream)
		{
			this.RequireServerSystemState(ServerSystemStatus.Uninitialized);
			System.Xml.Serialization.XmlSerializer xs = GetConfigSerializer();
			TelnetServerConfiguration<TTelnetSession> config = (TelnetServerConfiguration<TTelnetSession>)xs.Deserialize(stream);
			LoadConfiguration(config);
		}
		public void SaveConfiguration()
		{
			if(this._Configuration==null)
				throw new InvalidOperationException("You cannot have the TelnetServer save its configuration when it does not have one loaded.");
			this.SaveConfiguration(this.DefaultConfigurationFile);
		}
		public void SaveConfiguration(string fileName)
		{
			if(this._Configuration==null)
				throw new InvalidOperationException("You cannot have the TelnetServer save its configuration when it does not have one loaded.");
			System.IO.FileStream fs = new System.IO.FileStream(fileName,System.IO.FileMode.Create);
			SaveConfiguration(fs);
		}
		public void SaveConfiguration(System.IO.Stream stream)
		{
			if(this._Configuration==null)
				throw new InvalidOperationException("You cannot have the TelnetServer save its configuration when it does not have one loaded.");
			System.Xml.Serialization.XmlSerializer xs = this.GetConfigSerializer();
			xs.Serialize(stream,this._Configuration);
		}
		protected System.Xml.Serialization.XmlSerializer GetConfigSerializer()
		{
			return new System.Xml.Serialization.XmlSerializer(typeof(TelnetServerConfiguration<TTelnetSession>));
		}
		#endregion

        #region INetworkSystem Members

        [Browsable(true), DisplayName("Connection Listeners"), Description("The objects that are listening for new Telnet connections.")]
        public IConnectionListener[] GetConnectionListeners()
        {
            return ((IConnectionListener[])(this._Listeners.ToArray()));
        }

        #endregion

        protected override void _Dispose()
        {
            foreach (INetworkConnection c in _Connections)
            {
                c.Close();
                c.Dispose();
            }
            _Connections.Clear();
            foreach (TTelnetSession s in _Sessions)
            {
                s.SessionState.Clear();
                s.SessionState.Dispose();
                s.Dispose();
            }
            _Sessions.Clear();

            base._Dispose();
        }
    }
}
